package com.zhaojk.audio.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.RateLimiter;
import com.volcengine.service.visual.IVisualService;
import com.volcengine.service.visual.impl.VisualServiceImpl;
import com.volcengine.tos.*;
import com.volcengine.tos.comm.HttpMethod;
import com.volcengine.tos.comm.TosHeader;
import com.volcengine.tos.model.object.*;
import com.zhaojk.audio.domain.tos.TextToImageInput;
import com.zhaojk.audio.domain.tos.TextToImageOutput;
import com.zhaojk.audio.util.TimeTrace;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
 * 图片服务
 * @author zhaojk
 */
@Service
@Slf4j
public class PicServiceImpl {
    private IVisualService visualService;
    @Autowired
    private RateLimiter rateLimiter;

    @Autowired
    private OkHttpClient okHttpClient;

    @Autowired
    private ObjectMapper objectMapper;

    @Value("${volc.tos.accessKey}")
    private String accessKey;
    @Value("${volc.tos.secretKey}")
    private String secretKey;
    @Value("${volc.tos.endpoint}")
    private String endpoint;
    @Value("${volc.tos.region}")
    private String region;
    @Value("${volc.tos.bucketName}")
    private String bucketName;

    private IVisualService getVisualService(){
        if(this.visualService == null){
            this.visualService = VisualServiceImpl.getInstance();
            this.visualService.setAccessKey(accessKey);
            this.visualService.setSecretKey(secretKey);
        }
        return visualService;
    }

    public JSONObject queryPic(String prompt) {
        JSONObject req = new JSONObject();
        //请求Body(查看接口文档请求参数-请求示例，将请求参数内容复制到此)
        //req_key取固定值
        req.put("req_key", "high_aes_general_v21_L");
        req.put("prompt", prompt);
        req.put("req_schedule_conf", "general_v20_9B_pe");
        req.put("seed", -1);
        req.put("scale", 3.5);
        req.put("ddim_steps", 25);
        req.put("width", 256);
        req.put("height", 256);
        req.put("use_pre_llm", true);
        req.put("use_sr", false);
        req.put("return_url", true);
        //水印相关参数，需要创建logo信息类
        //LogonInfo logoInfo = LogonInfo.getInstance();
        //req.put("logo_info", logoInfo);

        try {
            JSONObject response = (JSONObject) this.getVisualService().cvProcess(req);
            log.info("" + JSON.toJSONString(response));
            return response;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 使用火山引擎的对象存储服务(tos)来存储文件
     * @param url 待上传的网络流地址
     * @param objectKey 对象名，模拟 uword/images 下的 apple.jpg
     *                  例如 uword/images/apple.jpg
     */
    public void upload2Tos(String url, String objectKey) {
        // 待上传的网络流地址 url = "https://p9-aiop-sign.byteimg.com/tos-cn-i-vuqhorh59i/2025062416455134950558A453629D641B-0~tplv-vuqhorh59i-image.image?rk3s=7f9e702d&x-expires=1750841156&x-signature=rxQd8yUYxCoAW4SSMV0k%2BqguX3w%3D";
        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);
        try(InputStream inputStream = new BufferedInputStream(new URL(url).openStream())){
            PutObjectInput putObjectInput = new PutObjectInput().setBucket(bucketName).setKey(objectKey).setContent(inputStream);
            PutObjectOutput output = tos.putObject(putObjectInput);
            log.info("putObject succeed, object's etag is " + output.getEtag());
            log.info("putObject succeed, object's crc64 is " + output.getHashCrc64ecma());
        } catch (IOException e) {
            log.info("putObject read data from url failed");
            e.printStackTrace();
        } catch (TosClientException e) {
            // 操作失败，捕获客户端异常，一般情况是请求参数错误，此时请求并未发送
            log.info("putObject failed");
            log.info("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败，捕获服务端异常，可以获取到从服务端返回的详细错误信息
            log.info("putObject failed");
            log.info("StatusCode: " + e.getStatusCode());
            log.info("Code: " + e.getCode());
            log.info("Message: " + e.getMessage());
            log.info("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常，一般不会执行到这里
            log.info("putObject failed");
            log.info("unexpected exception, message: " + t.getMessage());
        }
    }

    /**
     * 获取访问图片的url
     * 绑定限流资源
     *
     * @param prompt
     * @return
     */
    @SentinelResource(
            value = "volcPicResource",
            blockHandler = "handleBlockException"
    )
    public String getUrl(String prompt){
        // 预签名 URL 过期时间，单位为秒，设置3600秒即1小时后过期
        long expires = 3600;
        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);

        //1.查询url
        boolean isExist = isObjectsExistByPrefix("images/" + prompt + ".jpg", 1);
        if(isExist){
            //1.命中则返回url
            PreSignedURLInput urlInput = new PreSignedURLInput().setBucket(bucketName).setKey("images/" + prompt + ".jpg")
                    .setHttpMethod(HttpMethod.GET).setExpires(expires);
            PreSignedURLOutput urlOutput = tos.preSignedURL(urlInput);
            log.info("preSignedURL succeed, the signed url is " + urlOutput.getSignedUrl());
            String url = urlOutput.getSignedUrl();
            //4.将图片的url返回。
            if(url != null){
                return url;
            }
        }else{
            //2.没命中，调用大模型生成图片
            try{
                //调用大模型然后存储到tos。
                // 固定值
                String param = "aigc/text_to_image";
                String objectKey = "";
                Map<String, String> query = new HashMap<>(1);
                query.put(TosHeader.QUERY_DATA_PROCESS, param);
                PreSignedURLInput input = new PreSignedURLInput().setBucket(bucketName).setKey(objectKey).setQuery(query)
                        .setHttpMethod(HttpMethod.POST).setExpires(expires);
                PreSignedURLOutput output = tos.preSignedURL(input);
                log.info("preSignedURL succeed, the signed url is " + output.getSignedUrl());
                log.info("preSignedURL succeed, the signed header is " + output.getSignedHeader());
                TextToImageOutput textToImageOutput = doPostObject(output.getSignedUrl(), prompt);
                int code = textToImageOutput.getResp_json().getCode();
                if(code != 10000){
                    throw new Exception("图片生成失败");
                }else{
                    //获取图片访问url
                    PreSignedURLInput urlInput = new PreSignedURLInput().setBucket(bucketName).setKey("images/" + prompt + ".jpg")
                            .setHttpMethod(HttpMethod.GET).setExpires(expires);
                    PreSignedURLOutput urlOutput = tos.preSignedURL(urlInput);
                    log.info("preSignedURL succeed, the signed url is " + urlOutput.getSignedUrl());
                    String url = urlOutput.getSignedUrl();
                    //4.将图片的url入库并返回。
                    if(url != null){
                        return url;
                    }
                }
            } catch (IOException e) {
                log.error("doPostObject failed");
                e.printStackTrace();
            } catch (TosException e) {
                // 操作失败，捕获客户端异常，一般情况是请求参数错误，此时请求并未发送
                log.error("preSignedURL failed");
                log.info("Error Message: " + e.getMessage());
                if (e.getCause() != null) {
                    e.getCause().printStackTrace();
                }
            } catch (Throwable t) {
                // 作为兜底捕获其他异常，一般不会执行到这里
                log.error("get picture url failed");
                log.error("unexpected exception, message: " + t.getMessage());
            }
        }
        return null;
    }

    // 限流处理函数
    public String handleBlockException(BlockException ex) {
        return "请求过于频繁，请稍后再试";
    }

    private TextToImageOutput doPostObject(String url, String prompt) throws IOException{
        Request.Builder builder = new Request.Builder().url(url);

        TextToImageInput request = new TextToImageInput();
        //固定值
        request.setTaskType("text_to_image");
        request.setInputDataSource(new TextToImageInput.InputDataSource());
        request.getInputDataSource().setPrompt(prompt);
        request.setOutputProcessing(new TextToImageInput.OutputProcessing());
        request.getOutputProcessing().setBucket(bucketName);
        // 存储路径加上对象名
        request.getOutputProcessing().setObject("images/" + prompt + ".jpg");
        request.getOutputProcessing().setForbiddenOverwrite(false);
        //图片处理
        request.getOutputProcessing().setProcessActions("image/resize,w_256");
        request.setModelConfig(new TextToImageInput.ModelConfig());
        //固定值
        request.getModelConfig().setModelAction("CVProcess");
        //固定值
        request.getModelConfig().setModelVersion("2022-08-31");
        request.getModelConfig().setReqJson(new TextToImageInput.ModelConfig.ReqJson());
        //固定值
        request.getModelConfig().getReqJson().setReq_key("high_aes_general_v21_L");
        // 将请求对象转换为 JSON 字符串
        String jsonBody = objectMapper.writeValueAsString(request);
        // 发送请求
        builder.post(RequestBody.create(jsonBody, MediaType.parse("application/json; charset=utf-8")));

        // 发送请求并获取响应
        try (Response response = okHttpClient.newCall(builder.build()).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }
            // 将响应体转换为对象
            return objectMapper.readValue(response.body().string(), TextToImageOutput.class);
        }
    }
    /**
     * 检查图片是否存在
     */
    @TimeTrace("从TOS中检查图片是否存在")
    private boolean isObjectsExistByPrefix(String prefix, int maxKeys){
        TOSV2 tos = new TOSV2ClientBuilder().build(region, endpoint, accessKey, secretKey);
        try{
            boolean isTruncated = true;
            String continuationToken = null;
            while (isTruncated) {
                ListObjectsType2Input input = new ListObjectsType2Input().setBucket(bucketName)
                        .setMaxKeys(maxKeys).setPrefix(prefix).setContinuationToken(continuationToken);
                ListObjectsType2Output output = tos.listObjectsType2(input);
                log.info("listObjectsType2 succeed, is truncated? %b, next continuation token is %s.\n",
                        output.isTruncated(), output.getNextContinuationToken());
                if (output.getContents() != null) {
                    if(output.getContents().size() > 0){
                        return true;
                    }
                }
                if (output.getCommonPrefixes() != null) {
                    for (int i = 0; i < output.getCommonPrefixes().size(); i++) {
                        ListedCommonPrefix commonPrefix = output.getCommonPrefixes().get(i);
                        System.out.println("Listed commonPrefix is " + commonPrefix.getPrefix());
                    }
                }
                isTruncated = output.isTruncated();
                continuationToken = output.getNextContinuationToken();
            }
        } catch (TosClientException e) {
            // 操作失败，捕获客户端异常，一般情况是请求参数错误，此时请求并未发送
            System.out.println("listObjectsType2 failed");
            System.out.println("Message: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
        } catch (TosServerException e) {
            // 操作失败，捕获服务端异常，可以获取到从服务端返回的详细错误信息
            System.out.println("listObjectsType2 failed");
            System.out.println("StatusCode: " + e.getStatusCode());
            System.out.println("Code: " + e.getCode());
            System.out.println("Message: " + e.getMessage());
            System.out.println("RequestID: " + e.getRequestID());
        } catch (Throwable t) {
            // 作为兜底捕获其他异常，一般不会执行到这里
            System.out.println("listObjectsType2 failed");
            System.out.println("unexpected exception, message: " + t.getMessage());
        }
        return false;
    }
    /**
     * 获取图片内容
     */
    private String doGetObject(String url) throws IOException {
        Request.Builder builder = new Request.Builder().url(url);
        Response response = okHttpClient.newCall(builder.get().build()).execute();
        if (response == null || response.body() == null) {
            return null;
        }
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = response.body().byteStream().read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }
        result.flush();
        return result.toString("UTF-8");
    }
}
